package com.ease.io.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.*;

/**
 * todo 也可以用BIOClient当做客户端连接
 *
 * @author: chenhs
 * @date: Created in 15:09 2019/12/23
 **/
public class NewioServer {


    public static void main(String[] args) throws Exception {

        //极少数量线程池，当前cpu核心数是4，已用了1个，还有3个。
        int nThreads = 3;
        ExecutorService pool = new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());

        // 创建一个Selector
        Selector selector = Selector.open();

        ServerSocketChannel ssc = ServerSocketChannel.open();
        int port = 9200;
        ssc.bind(new InetSocketAddress(port));

        //注册到Selector上，让Selector帮忙检测连接进来
        //1. 要用非阻塞方式
        ssc.configureBlocking(false);
        //2. 注册
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        // 一个线程(当前main线程)负责选择就绪的Channel
        while (true) {
           // 阻塞选择就绪事件 select()是可以被中断的
           int readyChannelCount = selector.select();
           // select()是可以被中断的
           if (readyChannelCount == 0) {
               continue;
           }

           // 得到就绪的Channel
           Set<SelectionKey> selectionKeys = selector.selectedKeys();

           Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
           while (keyIterator.hasNext()) {
               SelectionKey key = keyIterator.next();
               if (key.isAcceptable()) {
                   // 连接进来了
                   // 接收连接
                   SocketChannel cc = ssc.accept();

                   // 注册到Selector，请它帮忙检测数据到来
                   cc.configureBlocking(false);
                   cc.register(selector, SelectionKey.OP_READ);

               } else if (key.isReadable()) {
                   // 数据已发过来了，该去读取处理了
                   // 交到线程池中去读取数据处理
                   pool.submit(new SocketProcess(key));
                   // 取消Selector注册；防止异步线程池处理不及时，重复选择（读取两次）
                   key.cancel();
               } else if (key.isWritable()) {
                   //可以发送出去了
               } else if (key.isConnectable()) {
                   //客户端用，连通服务器了
               }
               //处理了，一定要从SelectionKey移除
               keyIterator.remove();
           }

        }

    }

    private static Charset charset = Charset.forName("UTF-8");
    private static CharsetDecoder decoder = charset.newDecoder();

    static class SocketProcess implements Runnable {

        SelectionKey key;

        public SocketProcess(SelectionKey key) {
            this.key = key;
        }

        /**
         * todo 拓展：如何读取大于1024字节的数据？
         */
        @Override
        public void run() {
            SocketChannel sc = (SocketChannel) key.channel();
            // 读数据
            // 1.创建buffer
            ByteBuffer rbf = ByteBuffer.allocate(1024);
            try {
                // 如果是-1，说明已经读完了
                int readBytes = sc.read(rbf);
                // 转成读模式，不然用不了？（因为需要字节要转成字符）
                rbf.flip();
                System.out.println(decoder.decode(rbf).toString());

            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                // 当通道不再需要时，关闭。
                sc.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}
